// 《围棋》作者版权所有。保留所有权利。
// 此源代码的使用受BSD样式
// 许可证的约束，该许可证可以在许可证文件中找到。

// Package base定义了go命令的共享基本部分，尤其是日志和命令结构。
package base

import (
	"context"
	"flag"
	"fmt"
	exec "internal/execabs"
	"log"
	"os"
	"strings"
	"sync"

	"cmd/go/internal/cfg"
	"cmd/go/internal/str"
)

// 命令是go命令的一个实现
// 类似于go build或go fix。
type Command struct {
	// Run运行命令。
	// 参数是命令名后的参数。
	Run func(ctx context.Context, cmd *Command, args []string)

	// UsageLine是一行用法消息。
	// “go”和行中第一个标志或参数之间的单词被视为命令名。
	UsageLine string

	// Short是“go help”输出中显示的简短描述。
	Short string

	// Long是“go help<this command>”输出中显示的长消息。
	Long string

	// 标志是一组特定于此命令的标志。
	Flag flag.FlagSet

	// CustomFlags表示该命令将执行自己的
	// 标志解析。
	CustomFlags bool

	// Commands列出了可用的命令和帮助主题。
	// 这里的顺序是“go help”打印它们的顺序。
	// 注意，通常最好避免使用子命令。
	Commands []*Command
}

var Go = &Command{
	UsageLine: "go",
	Long:      `Go is a tool for managing Go source code.`,
	// 在包main中初始化的命令
}

// hasFlag报告一个命令或其任何子命令是否包含给定的
// 标志。
func hasFlag(c *Command, name string) bool {
	if f := c.Flag.Lookup(name); f != nil {
		return true
	}
	for _, sub := range c.Commands {
		if hasFlag(sub, name) {
			return true
		}
	}
	return false
}

// LongName返回命令的长名称：“go”和标志或参数之间的用法行中的所有单词，
func (c *Command) LongName() string {
	name := c.UsageLine
	if i := strings.Index(name, " ["); i >= 0 {
		name = name[:i]
	}
	if name == "go" {
		return ""
	}
	return strings.TrimPrefix(name, "go ")
}

// name返回命令的短名称：标志或参数之前用法行中的最后一个单词。
func (c *Command) Name() string {
	name := c.LongName()
	if i := strings.LastIndex(name, " "); i >= 0 {
		name = name[i+1:]
	}
	return name
}

func (c *Command) Usage() {
	fmt.Fprintf(os.Stderr, "usage: %s\n", c.UsageLine)
	fmt.Fprintf(os.Stderr, "Run 'go help %s' for details.\n", c.LongName())
	SetExitStatus(2)
	Exit()
}

// Runnable报告命令是否可以运行；否则
// 这是一个文档伪命令，比如importpath。
func (c *Command) Runnable() bool {
	return c.Run != nil
}

var atExitFuncs []func()

func AtExit(f func()) {
	atExitFuncs = append(atExitFuncs, f)
}

func Exit() {
	for _, f := range atExitFuncs {
		f()
	}
	os.Exit(exitStatus)
}

func Fatalf(format string, args ...any) {
	Errorf(format, args...)
	Exit()
}

func Errorf(format string, args ...any) {
	log.Printf(format, args...)
	SetExitStatus(1)
}

func ExitIfErrors() {
	if exitStatus != 0 {
		Exit()
	}
}

var exitStatus = 0
var exitMu sync.Mutex

func SetExitStatus(n int) {
	exitMu.Lock()
	if exitStatus < n {
		exitStatus = n
	}
	exitMu.Unlock()
}

func GetExitStatus() int {
	return exitStatus
}

// Run运行命令，stdout和stderr 
// 连接到go命令自己的stdout和stderr。
// 如果命令失败，Run将使用Errorf报告错误。
func Run(cmdargs ...any) {
	cmdline := str.StringList(cmdargs...)
	if cfg.BuildN || cfg.BuildX {
		fmt.Printf("%s\n", strings.Join(cmdline, " "))
		if cfg.BuildN {
			return
		}
	}

	cmd := exec.Command(cmdline[0], cmdline[1:]...)
	cmd.Stdout = os.Stdout
	cmd.Stderr = os.Stderr
	if err := cmd.Run(); err != nil {
		Errorf("%v", err)
	}
}

// RunStdin与run类似，但与Stdin相连。
func RunStdin(cmdline []string) {
	cmd := exec.Command(cmdline[0], cmdline[1:]...)
	cmd.Stdin = os.Stdin
	cmd.Stdout = os.Stdout
	cmd.Stderr = os.Stderr
	cmd.Env = cfg.OrigEnv
	StartSigHandlers()
	if err := cmd.Run(); err != nil {
		Errorf("%v", err)
	}
}

// 用法是用法报告功能，由包主
// 填写，但此处供其他包参考。
var Usage func()
